package com.boot.noRepeatSubmitAop.aspect;


import com.boot.noRepeatSubmit.KeyRuleEnum;
import com.boot.noRepeatSubmit.RedisLock;
import com.boot.noRepeatSubmitAop.aop.NoRepeatSubmitHigh;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 防止表单重复提交处理
 *
 * @author mkq
 * @data 2020/07/12 11:33:20
 */
@Slf4j
@Aspect
@Component
public class NoRepeatSubmitHihgAspect {


    @Autowired
    private RedisLock redisLock;

    private RedissonClient redissonClient;

    private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();


    /**
     * KEY的拼接符
     **/
    public static final String Link_Key_Tag = "\\+";

    /**
     * KEY的前缀
     **/
    public static final String Key_Prefix_Tag = "#";

    // 配置织入点
    @Pointcut("@annotation(com.boot.noRepeatSubmitAop.aop.NoRepeatSubmitHigh)")
    public void noRepeatSubmitPointCut() {
    }

    /**
     * 前置通知Before advice：在连接点前面执行，前置通知不会影响连接点的执行，除非此处抛出异常
     */
    @Around("noRepeatSubmitPointCut()")
    public void around(ProceedingJoinPoint point) throws Exception {
        handleNoRepeatSubmit(point);
        System.out.println("---------------@Before----------------");
    }


    /**
     * 异常返回通知After throwing advice：在连接点抛出异常后执行
     */
    @AfterThrowing("noRepeatSubmitPointCut()")
    public void afterThrowing() {
        System.out.println("---------------@AfterThrowing----------------");
    }


    protected Boolean handleNoRepeatSubmit(ProceedingJoinPoint joinPoint) {
        // 获得注解
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        NoRepeatSubmitHigh annotation = method.getAnnotation(NoRepeatSubmitHigh.class);
        String className = method.getDeclaringClass().getName();

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        // 组装key
        String key = this.createKey(joinPoint, annotation, request);

        Boolean success = null;
        // 锁的时间
        int lockTime = annotation.lockTime();

        // redis 锁 没有续命
//        String redisValGuid = UUID.randomUUID().toString().replace("-", "");
//        success = redisLock.lock(key, redisValGuid, lockTime);
        // redission 锁 自动续命 lockTime必须是30s，否则不会自动续命
        RLock lock = redissonClient.getLock(key);
        try {
            success = lock.tryLock(annotation.waitTime(), lockTime, annotation.timeUnit());
            if (success) {
                log.info(Thread.currentThread().getName() + " 加锁成功");
                // 放行方法执行
                joinPoint.proceed();
            } else {
                log.info(Thread.currentThread().getName() + " 加锁失败");
                throw new RuntimeException(annotation.alertMsg()); // 此处可以用return 返回错误 需要跟切的方法的返回值保持一致
            }

        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            if (success) {
                lock.unlock();
                // redis 解锁 如果不传入redisValGuid 存在解错锁的问题，需要自己写重写方法 用lus脚本删除锁
//                boolean unlock = redisLock.unlock(key, redisValGuid);
                log.info(Thread.currentThread().getName() + " 解锁成功");
            }
        }
        return success;
    }

    private String createKey(ProceedingJoinPoint joinPoint, NoRepeatSubmitHigh annotation, HttpServletRequest request) {
        String key = null;
        String requestURI = request.getRequestURI();
        String requestMethod = request.getMethod();
        String remoteAddr = request.getRemoteAddr();

        String userId = "100";
        KeyRuleEnum keyRuleEnum = annotation.keyRule();
        if (KeyRuleEnum.USER_PATH.equals(keyRuleEnum)) {
            key = userId + "." + requestURI;
        } else if (KeyRuleEnum.USER_PATH_KEY.equals(keyRuleEnum)) {
            key = userId + "." + requestURI + "." + this.getKeyVal(joinPoint, annotation, request);
        } else if (KeyRuleEnum.PATH_KEY.equals(keyRuleEnum)) {
            key = requestURI + "." + this.getKeyVal(joinPoint, annotation, request);
        } else if (KeyRuleEnum.KEY.equals(keyRuleEnum)) {
            key = this.getKeyVal(joinPoint, annotation, request);
        }

        return key;
    }

    private String getKeyVal(ProceedingJoinPoint joinPoint, NoRepeatSubmitHigh annotation, HttpServletRequest request) {

        String keys = annotation.keys();
        if (StringUtil.isBlank(keys)) {
            return null;
        }
        StringBuffer keysVal = new StringBuffer("");
        // 获取get请求参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        // 获取路径参数：使用@PathVariable注解传入的参数
        Map mapPath = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);

        for (String key : keys.split(Link_Key_Tag)) {
            String val = null;
            if (key.contains(".")) {
                val = getEntityField(joinPoint, key);
            } else {
                String key_ = key.replace(Key_Prefix_Tag, "");
                if (parameterMap.size() > 0 && parameterMap.containsKey(key_)) {
                    val = parameterMap.get(key_)[0];
                }
                if (mapPath.size() > 0 && mapPath.containsKey(key_)) {
                    val = mapPath.get(key_) + "";

                }
            }
            if (StringUtil.isNotBlank(val)) {
                keysVal.append(val).append("+");
            }

        }

        return keysVal.toString().substring(0, keysVal.toString().length() - 1);
    }


    // 如果key是对象属性
    private String getEntityField(ProceedingJoinPoint joinPoint, String key) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String[] params = discoverer.getParameterNames(method);
        Object[] args = joinPoint.getArgs();
        //将参数名与参数值对应起来
        EvaluationContext context = new StandardEvaluationContext();
        for (int len = 0; len < params.length; len++) {
            Object arg = args[len];
            context.setVariable(params[len], args[len]);
        }
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(key);
        Object expressionValue = expression.getValue(context, Object.class);
        return expressionValue == null ? null : expressionValue.toString();
    }


}
